import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'

import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import qs from 'qs'
import { config } from './config'
import { getAccessToken, getRefreshToken, getTenantId, setToken } from '@/utils/auth'
import errorCode, { Server_Error, Token_Error } from './errorCode'
import { useUserStoreWithOut } from '@/stores/modules/user'

import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { transParams } from '@/utils/lizard'

const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE === 'true'
const { base_url, result_code, request_timeout } = config

const refresh_token_url = '/system/auth/refresh-token'

// 是否在显示重新登录提示，确保同时只显示一个提示框
export const isRelogin = { show: false }

// 认证票据刷新时，积攒的请求
let requestList: any[] = []
// 认证票据是否刷新中
let isTokenRefreshing = false

const { wsCache: sessionWsCache } = useCache('sessionStorage')

// 创建axios实例
const service: AxiosInstance = axios.create({
  baseURL: base_url,
  timeout: request_timeout
  // 自定义参数序列化函数 待确认：评估与transParams的差异?
  // paramsSerializer: (params) => {
  //   return qs.stringify(params, { allowDots: true })
  // }
})

// request拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    if (config.__retries) {
      return config
    }
    config.__retries = 0
    // 是否忽略token头设置
    const ignoreToken = ((config as any).extension || {}).ignoreToken === true
    // 是否忽略重复提交防范检查
    const ignoreRepeatCheck = ((config as any).extension || {}).ignoreRepeatCheck === true

    // 处理认证token请求头
    if (!ignoreToken) {
      const accessToken = getAccessToken()
      if (accessToken) {
        config.headers['Authorization'] = 'Bearer ' + accessToken
      }
    }

    // 处理租户id请求头
    if (tenantEnable) {
      // 开启租户时，有则传递请求头，没有则不传递
      const tenantId = getTenantId()
      if (tenantId != undefined) {
        config.headers['tenant-id'] = tenantId
      }
    } else {
      // 未开启租户时，可以传递0，配合验证租户id=0的场景，服务端未开启租户时也不影响
      config.headers['tenant-id'] = 0
    }

    // 添加通用参数
    const genericUrlParams = {
      _platform: 'web'
    }

    // 插入通用参数到url
    const questMarkIdx = config.url!.indexOf('?')
    if (questMarkIdx >= 0) {
      config.url =
        config.url!.slice(0, questMarkIdx + 1) +
        qs.stringify(genericUrlParams) +
        (questMarkIdx == config.url!.length - 1 ? '' : '&' + config.url!.slice(questMarkIdx + 1))
    } else {
      config.url += '?' + qs.stringify(genericUrlParams)
    }

    const requestMethod = config.method?.toLowerCase()
    if (requestMethod === 'get') {
      // 防止 get 请求缓存
      config.headers['Cache-Control'] = 'no-cache'
      config.headers['Pragma'] = 'no-cache'
    } else if (requestMethod === 'post') {
      const contentType = config.headers['Content-Type'] || config.headers['content-type']
      if (contentType === 'application/json') {
        if (!config.data) {
          config.data = {}
        }
      }
    }

    // 自行处理url请求参数转换
    if (config.params) {
      const paramStr = transParams(config.params)
      if (paramStr) {
        config.url += (config.url!.includes('?') ? '&' : '?') + paramStr
        config.params = {}
      }
    }

    // upload文件上传不会走这个request.js，不会到这边，没有文件序列化性能问题
    if (!ignoreRepeatCheck && (requestMethod === 'post' || requestMethod === 'put')) {
      const requestObj = {
        url: config.url,
        params: typeof config.params === 'object' ? JSON.stringify(config.params) : config.params,
        data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
        time: new Date().getTime()
      }

      const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
      const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
      if (requestSize >= limitSize) {
        console.warn(`[${config.url}]: 请求数据大小超出允许的5M限制，无法进行防重复提交验证。`)
      } else {
        const lastRequestObj = sessionWsCache.get(CACHE_KEY.LastRequest)
        if (lastRequestObj === undefined || lastRequestObj === null || lastRequestObj === '') {
          sessionWsCache.set(CACHE_KEY.LastRequest, requestObj)
        } else {
          const l_url = lastRequestObj.url
          const l_params = lastRequestObj.params
          const l_data = lastRequestObj.data
          const l_time = lastRequestObj.time
          const interval = 1000 // 请求间隔时间(ms)，连续请求在此时间内做重复检查
          if (
            l_data === requestObj.data &&
            l_params === requestObj.params &&
            requestObj.time - l_time < interval &&
            l_url === requestObj.url
          ) {
            const { t } = useI18n()
            const message = t('common.repeatSubmit')
            console.warn(`[${config.url}]: ` + message)
            // 这里的reject，会跳过axios执行api请求，但依然会经过response interceptors的onRejected
            return Promise.reject(new Error(message))
          } else {
            sessionWsCache.set(CACHE_KEY.LastRequest, requestObj)
          }
        }
      }
    }

    // 若需要签名，可以在此处理签名(添加_ts与_sign请求参数)

    return config
  },
  (error: AxiosError) => {
    // 上面拦截处理逻辑中的Promise.reject不会再经过这里
    console.log('req err: ' + error)
    return Promise.reject(error)
  }
)

// response拦截器
service.interceptors.response.use(
  async (resp: AxiosResponse<any>) => {
    let respData = resp.data
    const { t } = useI18n()

    // 二进制数据则直接返回
    if (resp.request.responseType === 'blob' || resp.request.responseType === 'arraybuffer') {
      // 此时respData是blob/arraybuffer类型，而非json对象
      // response.data 若是一个 Blob 对象，则 type 属性返回的是该 Blob 对象的 MIME 类型，例如 image/jpeg 或 application/pdf
      // response.data 若是一个 ArrayBuffer 对象，则 type 属性返回undefined
      if (respData.type !== 'application/json') {
        return respData
      }
      // 获取blob/arraybuffer中响应json内容
      respData = await new Response(respData).json()
    }

    // respData此时为json对象或普通内容
    const code = (respData.code || result_code) as string
    const msg =
      errorCode[code] || respData.extensions?.realMessage || respData.msg || errorCode['default']

    const config = resp.config
    const errorSilent = ((config as any).extension || {}).errorSilent === true

    if (config.url?.includes(refresh_token_url)) {
      if (!code.startsWith('2')) {
        return Promise.reject(respData)
      } else {
        return respData
      }
    } else if (code === Token_Error && config.__retries! < 3) {
      const tokenErrorSilent = ((config as any).extension || {}).tokenErrorSilent === true
      if (tokenErrorSilent) {
        console.error(`[${config.url}]: ${code}, ${msg}`)
        return Promise.reject(respData)
      }
      // 允许最多重试两次，避免无限循环
      // token校验未通过(包括刷新token请求失败)
      if (!isTokenRefreshing) {
        // 放行一个刷新token的请求
        isTokenRefreshing = true
        // 若没有获取到refresh token，则直接处理最终认证未通过逻辑，这里也支持非oauth2形式的双token，可以仅access token方式
        if (!getRefreshToken()) {
          isTokenRefreshing = false
          return handleUnauthorized()
        }
        // 否则刷新访问令牌
        try {
          const refreshTokenResp = await refreshToken()
          // 刷新成功，回放队列请求
          setToken(refreshTokenResp.data)
          requestList.forEach((cb: any) => {
            cb()
          })
          requestList = []
          // 回放当前请求
          config.headers['Authorization'] = 'Bearer ' + getAccessToken()
          config.__retries!++
          return service(config)
        } catch (e) {
          // 刷新票据失败(系统异常/刷新票据已过期)
          // 仍重放队列请求，确保它们有回调
          requestList.forEach((cb: any) => {
            cb()
          })
          requestList = []
          if ((e as any).code === Token_Error) {
            // 刷新票据已失效，重新登录
            return handleUnauthorized()
          }
          // 其他类型错误，统一显示
          ElMessage.error(t('sys.api.refreshTokenFailed'))
          return Promise.reject(e)
        } finally {
          requestList = []
          isTokenRefreshing = false
        }
      } else {
        // 如果当前已在刷新令牌，则当前请求暂存，等待刷新令牌后触发重试
        return new Promise((resolve) => {
          requestList.push(() => {
            // 更新为最新的token
            config.headers['Authorization'] = 'Bearer ' + getAccessToken()
            config.__retries!++
            resolve(service(config))
          })
        })
      }
    } else if (code === Server_Error) {
      if (!errorSilent) {
        ElMessage.error(t('sys.api.errMsg500'))
      } else {
        console.error(`[${config.url}]: ${code}, ${msg}`)
      }
      return Promise.reject(respData)
    } else if (!code.startsWith('2')) {
      if (!errorSilent) {
        ElNotification.error({ title: msg })
      } else {
        console.error(`[${config.url}]: ${code}, ${msg}`)
      }
      return Promise.reject(respData)
    } else {
      return respData
    }
  },
  (error) => {
    // 超出2xx范围的状态码会触发该函数，request拦截器中的reject也会经过这里
    console.log('resp err: ' + error)
    let { message } = error
    if (message) {
      const { t } = useI18n()
      if (message === 'Network Error') {
        message = t('sys.api.errorMessage')
      } else if (message.includes('timeout')) {
        message = t('sys.api.apiTimeoutMessage')
      } else if (message.includes('Request failed with status code')) {
        message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3)
      }
      ElMessage({ message: message, type: 'error', duration: 5000 })
    }
    return Promise.reject(error)
  }
)

const refreshToken = () => {
  return service({
    url: refresh_token_url,
    method: 'post',
    data: {
      refreshToken: getRefreshToken()
    },
    extension: {
      ignoreToken: true,
      ignoreRepeatCheck: true
    }
  } as any)
}

const handleUnauthorized = () => {
  const { t } = useI18n()
  if (!isRelogin.show) {
    isRelogin.show = true
    ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
      showCancelButton: true,
      closeOnClickModal: false,
      showClose: true,
      closeOnPressEscape: false,
      confirmButtonText: t('login.relogin'),
      type: 'warning'
    })
      .then(() => {
        isRelogin.show = false
        useUserStoreWithOut()
          .logout()
          .then(() => {
            location.href = '/index'
          })
      })
      .catch(() => {
        isRelogin.show = false
      })
  }
  return Promise.reject(t('sys.api.timeoutMessage'))
}

export { service }
